package ca.scotthyndman.web.swf;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

import ca.scotthyndman.web.swf.common.Class;
import ca.scotthyndman.web.swf.common.Package;

/**
 * Outputs an exclusion file for MTASC given a set of files to compile.
 * 
 * @author Scott Hyndman
 */
public class BuildExclusionGenerator {

	static final String NEWLINE = System.getProperty("line.separator");

	String exFile;

	List<String> pkgs;

	List<String> cps;

	Package rootPackage;

	boolean mode;

	/**
	 * Constructs a new build exclusion generator.
	 * 
	 * @param excludesFile
	 *            The path to the exclude file to write.
	 * @param packages
	 *            Fully-qualified package names of packages that are packed. If
	 *            the package names end in ".*", then all child packages will
	 *            also be excluded.
	 * @param classPaths
	 *            Paths to directories that act as class paths.
	 * @param excludeMode
	 *            <code>true</code> if <code>packages</code> represents
	 *            packages to exclude, or <code>false</code> if
	 *            <code>packages</code> represents packages to pack.
	 * @throws Exception
	 */
	public BuildExclusionGenerator(String excludesFile, List<String> packages,
			List<String> classPaths, boolean excludeMode) throws Exception {
		this.exFile = excludesFile;
		this.pkgs = packages;
		this.cps = classPaths;
		this.mode = excludeMode;

		generatePackages();

		computeIncludedPackages();

		generateExcludeFile();
	}

	/**
	 * Generates all top-level packages from the class paths.
	 * 
	 * @throws Exception
	 */
	public void generatePackages() throws Exception {
		rootPackage = new Package("");

		for (String cp : cps) {
			File cpDir = new File(cp);
			if (!cpDir.isDirectory()) {
				throw new Exception("One of the classpaths (" + cp
						+ ") is not a directory");
			}
			rootPackage.merge(generatePackage(cpDir, true));
		}
	}

	/**
	 * Generates a package structure.
	 * 
	 * @param dir
	 * @param isRoot
	 * @return
	 */
	private Package generatePackage(File dir, boolean isRoot) {
		Package pkg = new Package(isRoot ? "" : dir.getName());
		for (File f : dir.listFiles()) {
			if (f.isDirectory() && !f.isHidden()) {
				pkg.addChildPackage(generatePackage(f, false));
			} else if (f.isFile() && f.getName().endsWith("as")) {
				pkg.addChildClass(f.getName().replace(".as", ""));
			}
		}
		return pkg;
	}

	/**
	 * Determines the included packages.
	 */
	private void computeIncludedPackages() {
		//
		// Mark certain packages as being packed
		//
		for (String pkgName : pkgs) {
			boolean packChildren = false;
			if (pkgName.charAt(pkgName.length() - 1) == '*') {
				packChildren = true;
				pkgName = pkgName.substring(0, pkgName.lastIndexOf('.'));
			}

			Class pkg = rootPackage.getPackageNamed(pkgName);
			if (pkg == null) {
				System.err.println("Package named " + pkgName + " not found");
				continue;
			}

			pkg.setPacked(true);

			if (pkg instanceof Package) {
				((Package) pkg).setPackChildren(packChildren);
			}
		}
	}

	/**
	 * Generates an exclude file.
	 */
	public void generateExcludeFile() {
		String contents = excludesForPackage(rootPackage);

		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(exFile));
			out.write(contents);
			out.close();
		} catch (IOException e) {
		}
	}

	/**
	 * Returns the excludes for a given package.
	 * 
	 * @param pkg
	 * @return
	 */
	private String excludesForPackage(Package pkg) {
		StringBuffer buf = new StringBuffer();

		if (pkg.getParent() != null && mode == pkg.isPacked()) {
			String pkgFullName = pkg.getFullName();
			for (Class cls : pkg.getChildClasses().values()) {
				if (mode != cls.isPacked()) {
					continue;
				}

				buf.append(pkgFullName);
				buf.append('.');
				buf.append(cls.getName());
				buf.append(NEWLINE);
			}
		}
		if (mode == pkg.areChildrenPacked()) {
			for (Package child : pkg.getChildPackages().values()) {
				buf.append(excludesForPackage(child));
			}
		}

		return buf.toString();
	}
}
